/*
 *  (c) 2010-2012 Ashwin Rayaprolu (http://linkwithweb.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 *  file except in compliance with the License. You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the
 *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 *  either express or implied. See the License for the specific language governing permissions
 *  and limitations under the License.
 */
 
SwiftUI.dndMgrList = [];

SwiftUI.registerDraggable = function(aDraggable, mgrIdx) {
  if (typeof mgrIdx != 'number') mgrIdx=0;
  if (typeof SwiftUI.dndMgrList[mgrIdx] != 'object')
    SwiftUI.dndMgrList[mgrIdx] = new SwiftUI.dndMgr();
  SwiftUI.dndMgrList[mgrIdx].registerDraggable(aDraggable);
};

SwiftUI.registerDropZone = function(aDropZone, mgrIdx) {
  if (typeof mgrIdx != 'number') mgrIdx=0;
  if (typeof SwiftUI.dndMgrList[mgrIdx] != 'object')
    SwiftUI.dndMgrList[mgrIdx] = new SwiftUI.dndMgr();
  SwiftUI.dndMgrList[mgrIdx].registerDropZone(aDropZone);
};

SwiftUI.dndMgr = function() {
  this.initialize();
};

SwiftUI.dndMgr.prototype = {
/**
 * @class Implements drag-n-drop manager -- a group of linked draggables and drop zones
 * @constructs
 */
   initialize: function() {
      this.dropZones                = [];
      this.draggables               = [];
      this.currentDragObjects       = [];
      this.dragElement              = null;
      this.lastSelectedDraggable    = null;
      this.currentDragObjectVisible = false;
      this.interestedInMotionEvents = false;
      this._mouseDown = SwiftUI.eventHandle(this,'_mouseDownHandler');
      this._mouseMove = SwiftUI.eventHandle(this,'_mouseMoveHandler');
      this._mouseUp = SwiftUI.eventHandle(this,'_mouseUpHandler');
   },

   registerDropZone: function(aDropZone) {
      this.dropZones[ this.dropZones.length ] = aDropZone;
   },

   deregisterDropZone: function(aDropZone) {
      var newDropZones = new Array();
      var j = 0;
      for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
         if ( this.dropZones[i] != aDropZone )
            newDropZones[j++] = this.dropZones[i];
      }

      this.dropZones = newDropZones;
   },

   clearDropZones: function() {
      this.dropZones = new Array();
   },

   registerDraggable: function( aDraggable ) {
      this.draggables[ this.draggables.length ] = aDraggable;
      var htmlElement = aDraggable.getMouseDownHTMLElement();
      if ( htmlElement != null ) { 
         htmlElement.swiftuiDraggable = aDraggable;
         SwiftUI.eventBind(htmlElement, "mousedown", SwiftUI.eventHandle(this,'_attachEvents'));
         SwiftUI.eventBind(htmlElement, "mousedown", this._mouseDown);
      }
   },

   clearSelection: function() {
      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
         this.currentDragObjects[i].deselect();
      this.currentDragObjects = new Array();
      this.lastSelectedDraggable = null;
   },

   hasSelection: function() {
      return this.currentDragObjects.length > 0;
   },

   setStartDragFromElement: function( e, mouseDownElement ) {
      this.origPos = SwiftUI.cumulativeOffset(mouseDownElement);
      var coord=SwiftUI.eventClient(e);
      this.startx = coord.x - this.origPos.left;
      this.starty = coord.y - this.origPos.top;

      this.interestedInMotionEvents = this.hasSelection();
      SwiftUI.eventStop(e);
   },

   updateSelection: function( draggable, extendSelection ) {
      if ( ! extendSelection )
         this.clearSelection();

      if ( draggable.isSelected() ) {
         this.currentDragObjects=this.currentDragObjects.without(draggable);
         draggable.deselect();
         if ( draggable == this.lastSelectedDraggable )
            this.lastSelectedDraggable = null;
      }
      else {
         draggable.select();
         if ( draggable.isSelected() ) {
           this.currentDragObjects.push(draggable);
           this.lastSelectedDraggable = draggable;
         }
      }
   },

   _mouseDownHandler: function(e) {
      // if not button 1 ignore it...
      if (!SwiftUI.eventLeftClick(e)) return;

      var eventTarget      = SwiftUI.eventElement(e);
      var draggableObject  = eventTarget.swiftuiDraggable;

      var candidate = eventTarget;
      while (draggableObject == null && candidate.parentNode) {
         candidate = candidate.parentNode;
         draggableObject = candidate.swiftuiDraggable;
      }
   
      if ( draggableObject == null ) return;

      this.updateSelection( draggableObject, e.ctrlKey );

      // clear the drop zones postion cache...
      if ( this.hasSelection() ) {
         for ( var i = 0 ; i < this.dropZones.length ; i++ )
            this.dropZones[i].clearPositionCache();
      }
      this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
   },


   _mouseMoveHandler: function(e) {
      if ( !this.interestedInMotionEvents ) {
         return;
      }

      if ( ! this.hasSelection() )
         return;

      if ( ! this.currentDragObjectVisible )
         this._startDrag(e);

      if ( !this.activatedDropZones )
         this._activateRegisteredDropZones();

      this._updateDraggableLocation(e);
      this._updateDropZonesHover(e);

      SwiftUI.eventStop(e);
   },

   _makeDraggableObjectVisible: function(e) {
      if ( !this.hasSelection() )
         return;

      var dragElement;
      if ( this.currentDragObjects.length > 1 )
         dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
      else
         dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();

      // go ahead and absolute position it...
      this.dragElemPosition=SwiftUI.getStyle(dragElement, "position");
      if (this.dragElemPosition != "absolute")
         dragElement.style.position = "absolute";

      // need to parent him into the document...
      if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
         document.body.appendChild(dragElement);

      this.dragElement = dragElement;
      this._updateDraggableLocation(e);

      this.currentDragObjectVisible = true;
   },

   _leftOffset: function(e) {
	   return e.offsetX ? document.body.scrollLeft : 0;
	},

   _topOffset: function(e) {
	   return e.offsetY ? document.body.scrollTop : 0;
	},

		
   _updateDraggableLocation: function(e) {
      var dragObjectStyle = this.dragElement.style;
      var coord=SwiftUI.eventClient(e);
      dragObjectStyle.left = (coord.x + this._leftOffset(e) - this.startx) + "px";
      dragObjectStyle.top  = (coord.y + this._topOffset(e) - this.starty) + "px";
   },

   _updateDropZonesHover: function(e) {
      var i,n = this.dropZones.length;
      for ( i = 0 ; i < n ; i++ ) {
         if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
            this.dropZones[i].hideHover();
      }

      for ( i = 0 ; i < n ; i++ ) {
         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
            if ( this.dropZones[i].canAccept(this.currentDragObjects) )
               this.dropZones[i].showHover();
         }
      }
   },

   _startDrag: function(e) {
      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
         this.currentDragObjects[i].startDrag();
      this._makeDraggableObjectVisible(e);
   },

   _mouseUpHandler: function(e) {
      if ( ! this.hasSelection() ) return;
      if (!SwiftUI.eventLeftClick(e)) return;

      this.interestedInMotionEvents = false;

      if ( this._placeDraggableInDropZone(e) )
         this._completeDropOperation(e);
      else if (this.dragElement != null) {
         SwiftUI.eventStop(e);
         SwiftUI.animate(this.dragElement, 
                      {duration: 300, onEnd:SwiftUI.bind(this,'_doCancelDragProcessing')}, 
                      {left:this.origPos.left, top:this.origPos.top});
      }

     SwiftUI.eventUnbind(document.body, "mousemove", this._mouseMove);
     SwiftUI.eventUnbind(document.body, "mouseup",  this._mouseUp);
   },

   _retTrue: function () {
      return true;
   },

   _completeDropOperation: function(e) {
      if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
         if ( this.dragElement.parentNode != null )
            this.dragElement.parentNode.removeChild(this.dragElement);
      }

      this._deactivateRegisteredDropZones();
      this._endDrag();
      this.clearSelection();
      this.dragElement = null;
      this.currentDragObjectVisible = false;
      SwiftUI.eventStop(e);
   },

   _doCancelDragProcessing: function() {
      this._cancelDrag();
      if ( this.dragElement == this.currentDragObjects[0].getMouseDownHTMLElement() ) {
         this.dragElement.style.position=this.dragElemPosition;
      } else {
         if ( this.dragElement && this.dragElement.parentNode != null )
            this.dragElement.parentNode.removeChild(this.dragElement);
      }
      this._deactivateRegisteredDropZones();
      this.dragElement = null;
      this.currentDragObjectVisible = false;
   },

   _placeDraggableInDropZone: function(e) {
      var foundDropZone = false;
      var n = this.dropZones.length;
      for ( var i = 0 ; i < n ; i++ ) {
         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
            if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
               this.dropZones[i].hideHover();
               this.dropZones[i].accept(this.currentDragObjects);
               foundDropZone = true;
               break;
            }
         }
      }

      return foundDropZone;
   },

   _cancelDrag: function() {
      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
         this.currentDragObjects[i].cancelDrag();
   },

   _endDrag: function() {
      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
         this.currentDragObjects[i].endDrag();
   },

   _mousePointInDropZone: function( e, dropZone ) {

      var absoluteRect = dropZone.getAbsoluteRect();
      var coord=SwiftUI.eventClient(e);

      return coord.x  > absoluteRect.left + this._leftOffset(e) &&
             coord.x  < absoluteRect.right + this._leftOffset(e) &&
             coord.y  > absoluteRect.top + this._topOffset(e)   &&
             coord.y  < absoluteRect.bottom + this._topOffset(e);
   },

   _activateRegisteredDropZones: function() {
      var n = this.dropZones.length;
      for ( var i = 0 ; i < n ; i++ ) {
         var dropZone = this.dropZones[i];
         if ( dropZone.canAccept(this.currentDragObjects) )
            dropZone.activate();
      }

      this.activatedDropZones = true;
   },

   _deactivateRegisteredDropZones: function() {
      var n = this.dropZones.length;
      for ( var i = 0 ; i < n ; i++ )
         this.dropZones[i].deactivate();
      this.activatedDropZones = false;
   },

   _attachEvents: function () {
     SwiftUI.eventBind(document.body, "mousemove", this._mouseMove);
     SwiftUI.eventBind(document.body, "mouseup",  this._mouseUp);
   }

};


SwiftUI.Draggable = function(type, htmlElement) {
  this.initialize(type, htmlElement);
};

SwiftUI.Draggable.prototype = {
/**
 * @class Implements behavior for a draggable element
 * @constructs
 */
   initialize: function( type, htmlElement ) {
      this.type          = type;
      this.htmlElement   = SwiftUI.$(htmlElement);
      this.selected      = false;
   },

   /**
    *   Returns the HTML element that should have a mouse down event
    *   added to it in order to initiate a drag operation
    **/
   getMouseDownHTMLElement: function() {
      return this.htmlElement;
   },

   select: function() {
      this._select();
   },

   _select: function() {
      this.selected = true;
      if (this.showingSelected) return;
      this.showingSelected = true;

      var htmlElement = this.getMouseDownHTMLElement();
      var color = SwiftUI.Color.createColorFromBackground(htmlElement);
      color.isBright() ? color.darken(0.033) : color.brighten(0.033);
      this.saveBackground = SwiftUI.getStyle(htmlElement, "backgroundColor", "background-color");
      htmlElement.style.backgroundColor = color.asHex();
   },

   deselect: function() {
      this.selected = false;
      if (!this.showingSelected) return;
      var htmlElement = this.getMouseDownHTMLElement();
      htmlElement.style.backgroundColor = this.saveBackground;
      this.showingSelected = false;
   },

   isSelected: function() {
      return this.selected;
   },

   startDrag: function() {
   },

   cancelDrag: function() {
   },

   endDrag: function() {
   },

   getSingleObjectDragGUI: function() {
      return this.htmlElement;
   },

   getMultiObjectDragGUI: function( draggables ) {
      return this.htmlElement;
   },

   getDroppedGUI: function() {
      return this.htmlElement;
   },

   toString: function() {
      return this.type + ":" + this.htmlElement + ":";
   }

};


SwiftUI.LiveGridDraggable = function(grid, rownum, colnum) {
  this.initialize(grid, rownum, colnum);
};

SwiftUI.LiveGridDraggable.prototype = SwiftUI.extend(new SwiftUI.Draggable(), {
/**
 * @class Enables draggable behavior for LiveGrid cells.
 * Called by LiveGrid#appendBlankRow for columns where canDrag is true.
 * @extends SwiftUI.Draggable
 * @constructs
 */
  initialize: function( grid, rownum, colnum) {
    this.type        = 'SwiftUICell';
    this.htmlElement = grid.cell(rownum,colnum);
    this.liveGrid    = grid;
    this.dragRow     = rownum;
    this.dragCol     = colnum;
  },
  
  select: function() {
    if (this.dragRow >= this.liveGrid.buffer.totalRows) return;
    this.selected = true;
    this.showingSelected = true;
  },

  deselect: function() {
    this.selected = false;
    this.showingSelected = false;
  },

  getSingleObjectDragGUI: function() {
    var div = document.createElement("div");
    div.className = 'LiveGridDraggable';
    div.style.width = (this.htmlElement.offsetWidth - 10) + "px";
    div.innerHTML=this.htmlElement.innerHTML;
    return div;
  }
});


SwiftUI.Dropzone = function(htmlElement) {
  this.initialize(htmlElement);
};

SwiftUI.Dropzone.prototype = {
/**
 * @class Implements behavior for a drop zone
 * @constructs
 */
   initialize: function( htmlElement ) {
      this.htmlElement  = SwiftUI.$(htmlElement);
      this.absoluteRect = null;
   },

   getHTMLElement: function() {
      return this.htmlElement;
   },

   clearPositionCache: function() {
      this.absoluteRect = null;
   },

   getAbsoluteRect: function() {
      if ( this.absoluteRect == null ) {
         var htmlElement = this.getHTMLElement();
         var pos = SwiftUI.viewportOffset(htmlElement);

         this.absoluteRect = {
            top:    pos.top,
            left:   pos.left,
            bottom: pos.top + htmlElement.offsetHeight,
            right:  pos.left + htmlElement.offsetWidth
         };
      }
      return this.absoluteRect;
   },

   activate: function() {
      var htmlElement = this.getHTMLElement();
      if (htmlElement == null  || this.showingActive)
         return;

      this.showingActive = true;
      this.saveBackgroundColor = htmlElement.style.backgroundColor;

      var fallbackColor = "#ffea84";
      var currentColor = SwiftUI.Color.createColorFromBackground(htmlElement);
      if ( currentColor == null )
         htmlElement.style.backgroundColor = fallbackColor;
      else {
         currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
         htmlElement.style.backgroundColor = currentColor.asHex();
      }
   },

   deactivate: function() {
      var htmlElement = this.getHTMLElement();
      if (htmlElement == null || !this.showingActive)
         return;

      htmlElement.style.backgroundColor = this.saveBackgroundColor;
      this.showingActive = false;
      this.saveBackgroundColor = null;
   },

   showHover: function() {
      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null || this.showingHover )
         return;

      this.saveBorderWidth = htmlElement.style.borderWidth;
      this.saveBorderStyle = htmlElement.style.borderStyle;
      this.saveBorderColor = htmlElement.style.borderColor;

      this.showingHover = true;
      htmlElement.style.borderWidth = "1px";
      htmlElement.style.borderStyle = "solid";
      //htmlElement.style.borderColor = "#ff9900";
      htmlElement.style.borderColor = "#ffff00";
   },

   hideHover: function() {
      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null || !this.showingHover )
         return;

      htmlElement.style.borderWidth = this.saveBorderWidth;
      htmlElement.style.borderStyle = this.saveBorderStyle;
      htmlElement.style.borderColor = this.saveBorderColor;
      this.showingHover = false;
   },

   canAccept: function(draggableObjects) {
      return true;
   },

   accept: function(draggableObjects) {
      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null )
         return;

      var n = draggableObjects.length;
      for ( var i = 0 ; i < n ; i++ ) {
         var theGUI = draggableObjects[i].getDroppedGUI();
         if ( SwiftUI.getStyle( theGUI, "position" ) == "absolute" ) {
            theGUI.style.position = "static";
            theGUI.style.top = "";
            theGUI.style.top = "";
         }
         htmlElement.appendChild(theGUI);
      }
   }
};

SwiftUI.includeLoaded('SwiftUIDragDrop.js');
